import logging
import struct
import time

from PyQt5.QtCore import QThread, pyqtSignal

import errorhandle

import socket
import traceback

from tools import parser
from tools.parser import INFO_PACKAGE_LENGTH, DATA_PACKAGE_LENGTH
from tools.tools import save_data


class UDPServer(QThread):
    trigger = pyqtSignal(list)
    triggerInfo = pyqtSignal(list)
    triggerExcept = pyqtSignal(int)

    def __init__(self, server_config):
        super().__init__()
        self.server_config = server_config
        self.isConnected = False
        self.udpSocket = None
        self.isRunning = False
        self.rawData = ''

    def connect_server(self):
        try:
            if self.udpSocket:
                self.udpSocket.close()
            self.udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            self.hostAddr = (self.server_config["HOST_ADDR"], self.server_config["HOST_PORT"])
            self.deviceAddr = (self.server_config["IP_ADDR"], self.server_config["PORT"])
            self.udpSocket.bind(self.hostAddr)
            self.isConnected = True
            return 0
        except Exception as e:
            traceback.print_exc()
            self.isConnected = False
            self.triggerExcept.emit(errorhandle.START_SERVER_EXCEPTION)
            return errorhandle.START_SERVER_EXCEPTION

    def send(self, data):
        """
        Send data to radar, beware that the data must be converted to bytes, or exception occur!

        :param data: the bytes data which is supposed to send
        :return: 0 if success
                 errorhandle.SEND_INSTRUCT_ERROR if an exception when sending(返回的数字和发送byte长度不一，或者发送失败）
                 errorhandle.EMPTY_DATA_ERROR if an empty bytes is checked(所要发送的字节为空）
                 errorhandle.DISCONNECT_ERROR if the connexion is out(连接已经断了)
        """
        if data is None or len(data) == 0:
            logging.error("The data is empty...ERROR CODE: " + str(errorhandle.EMPTY_DATA_ERROR))
            return errorhandle.EMPTY_DATA_ERROR
        if self.isConnected:
            tryTimes = 0
            while True:
                try:
                    logging.info("Trying to send data.." + str(data))
                    res = self.udpSocket.sendto(data, self.deviceAddr)
                    logging.info("Data is sent: " + str(res))
                    if res != len(data):
                        logging.error("NET send data failure..Error Code:" + str(errorhandle.SEND_INSTRUCT_ERROR))
                        return errorhandle.SEND_INSTRUCT_ERROR
                    else:
                        return 0
                except Exception as e:
                    if tryTimes < 3:
                        time.sleep(0.1)
                        logging.error(e)
                        tryTimes += 1
                        continue
                    else:
                        self.isConnected = False
                        logging.error(str(e) + ', ERROR CODE:' + str(errorhandle.SEND_INSTRUCT_ERROR))
                        return errorhandle.SEND_INSTRUCT_ERROR
        else:
            logging.error("Socket is disconnected when trying to send data.." + str(errorhandle.DISCONNECT_ERROR))
            return errorhandle.DISCONNECT_ERROR

    def recv(self, recvSize):
        """
        Receive data from radar with radar mode 测距轮

        :param recvSize: the bytes size
        :return: the byte data received from radar
                 -1 if the wheels is not moving, just ignore it
                 errorhandle.DISCONNECT_ERROR if the connexion is down
        """
        if self.isConnected:
            try:
                data, client = self.udpSocket.recvfrom(recvSize)
                return data, client
            except Exception as e:
                # traceback.print_exc()
                return -1
        else:
            logging.error("Socket disconnect when trying to send data..")
            return errorhandle.DISCONNECT_ERROR

    def reconnect(self):
        time.sleep(0.2)
        return self.connect_server()

    def disconnect(self):
        self.isConnected = False
        self.udpSocket.close()

    def hexstr_to_hex(self, hexStr):
        hexes = hexStr.split(' ')
        res = b''
        for ele in hexes:
            temp = int(ele, 16)
            res = res + struct.pack('B', temp)
        return res

    def execute_cmd(self, cmd='init', para=b''):
        dataHex = b''
        if cmd == 'init':
            init = self.server_config['INIT_CMD']
            dataHex = struct.pack("%dB" % (len(init)), *init)
        elif cmd == 'start':
            dataHex = para
        elif cmd == 'reset':
            dataHex = para
        elif cmd == 'stop':
            dataHex = struct.pack("B", 0x00)
        return self.send(dataHex)

    def save_rawdata(self):
        if len(self.rawData) > 0:
            res = save_data(self.rawData, instType='uwbHex', filetype='txt')
            if res == errorhandle.SAVE_DAVE_ERROR:
                logging.error("SAVE RAW DATA EXCEPTION")
            else:
                logging.error("SAVE RAW DATA DONE at: " + str(res))

    def run(self):
        self.rawData = []
        if self.isConnected:
            while self.isRunning:
                try:
                    recvData, client = self.recv(self.server_config['DATA_RECV_MAX_BYTE'])
                    if len(recvData) == INFO_PACKAGE_LENGTH:
                        data = parser.parse_info(recvData)
                    elif len(recvData) == DATA_PACKAGE_LENGTH:
                        self.rawData.append(recvData.hex())
                        data = parser.parse_data(recvData)
                    if type(data) == int:
                        self.triggerExcept.emit(data)
                        continue
                    else:
                        if len(data) > 4:
                            self.trigger.emit(data)
                        elif len(data) == 4 and type(data[0]) == bytes:
                            self.triggerInfo.emit(data)
                except TypeError:
                    logging.error("Type Error : Ignore...")
                except Exception:
                    traceback.print_exc()
                    self.triggerExcept.emit(-1)

                time.sleep(self.server_config['FREQ'])

    def stop(self):

        self.isRunning = False
